﻿//Copyright 2012 - Jonathan Allen

using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Controls;
using System.Windows.Navigation;
using System.Windows.Input;
using System.Diagnostics;
using Granite.Xaml.Internals;
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Contracts;
using System.Globalization;
using System.Security.Permissions;
using System.Security;


#if WINDOWS_PHONE
using Microsoft.Phone.Controls;
using System.Windows;
using Microsoft.Phone.Tasks;
#endif

namespace Granite.Xaml
{
	/// <summary>
	/// Base class for view models for navigation-style applications. 
	/// </summary>
	public partial class NavigationViewModel : ViewModelBase
	{
		private const string NavigatorMissing = "The navigator has not been set.";
		INavigator m_Navigator;

		/// <summary>
		/// Initializes a new instance of the NavigationViewModel class in an incomplete state.
		/// </summary>
		/// <remarks>This constructor is normally used when the view-model is created before the navigator that uses it.</remarks>
		public NavigationViewModel()
		{

		}

		/// <summary>
		/// Initializes a new instance of the NavigationViewModel class.
		/// </summary>
		/// <param name="navigator"></param>
		/// <remarks>This constructor is normally used for passing in a mock navigator.</remarks>
		public NavigationViewModel(INavigator navigator)
		{
			if (navigator == null)
				throw new ArgumentNullException("navigator", "navigator is null.");
			Contract.EndContractBlock();

			m_Navigator = navigator;
		}

		/// <summary>
		/// Changes the navigator associated with this view-model
		/// </summary>
		/// <param name="navigator"></param>
		public void SetNavigator(INavigator navigator)
		{
			if (navigator == null)
				throw new ArgumentNullException("navigator", "navigator is null.");
			Contract.EndContractBlock();

			Navigator = navigator;
		}

		/// <summary>
		/// Navigate to the indicated view. The view name will be translated into a URI using the NavigationFormatString.
		/// </summary>
		/// <param name="viewName"></param>
		/// <remarks>If the NavigationFormatString is empty the viewName is used unaltered as the URI </remarks>
		public void Navigate(string viewName)
		{
			Navigate(viewName, null);
		}

		/// <summary>
		/// Navigate to the indicated view. The view name will be translated into a URI using the NavigationFormatString.
		/// </summary>
		/// <param name="viewName"></param>
		/// <param name="queryString">The querystring is appended to the URI used for navigationS</param>
		/// <remarks>If the NavigationFormatString is empty the viewName is used unaltered as the URI </remarks>
		public void Navigate(string viewName, string queryString)
		{
			if (viewName == null)
				throw new ArgumentNullException("viewName", "viewName is null.");
			if (String.IsNullOrEmpty(viewName))
				throw new ArgumentException("viewName is empty.", "viewName");
			if (Navigator == null)
				throw new InvalidOperationException(NavigatorMissing);
			Contract.EndContractBlock();

			if (!string.IsNullOrEmpty(queryString) && !queryString.StartsWith("?", StringComparison.Ordinal))
				queryString = "?" + queryString;

			var format = NavigationFormatString;
			if (StringUtilities.IsNullOrWhiteSpace(format))
			{
				Contract.Assume(Navigator != null);
				Navigator.Navigate(new Uri(viewName + queryString, UriKind.Relative));
			}
			else
			{
				Contract.Assume(Navigator != null);
				Contract.Assume(format != null);
				Navigator.Navigate(new Uri(String.Format(CultureInfo.InvariantCulture, format, viewName) + queryString, UriKind.Relative));
			}
		}

#if WPF || WINDOWS_PHONE
		/// <summary>
		/// Navigates to the indicated page.
		/// </summary>
		/// <param name="pageType"></param>
		public void Navigate(Type pageType)
		{
			if (pageType == null)
				throw new ArgumentNullException("pageType", "pageType is null.");
			if (Navigator == null)
				throw new InvalidOperationException(NavigatorMissing);
			Contract.EndContractBlock();

			var assemblyName = pageType.Assembly.FullName;
			assemblyName = assemblyName.Substring(0, assemblyName.IndexOf(",", StringComparison.Ordinal));

			var pageName = pageType.FullName;
			pageName = pageName.Substring(assemblyName.Length + 1);
			pageName = pageName.Replace(".", "/");

			var targetString = string.Format(System.Globalization.CultureInfo.InvariantCulture, "/{0};component/{1}.xaml", assemblyName, pageName);
			var targetUri = new Uri(targetString, UriKind.Relative);
			Contract.Assume(Navigator != null);
			Navigator.Navigate(targetUri);
		}
#endif

		/// <summary>
		/// This is the format string used to map views to a XAML URI.
		/// By default it uses a format needed to load pages from a folder named "Views".
		/// </summary>
		/// <remarks>To disable view-name formatting set this value to null.</remarks>
		public string NavigationFormatString
		{

			get
			{
#if WPF
				const string defaultFormat = @"/Views/{0}.xaml";
#endif
#if SILVERLIGHT && !WINDOWS_PHONE
				const string defaultFormat = @"/{0}";
#endif
#if WINDOWS_PHONE
				const string defaultFormat = @"/Views/{0}.xaml";
#endif

				return Get<string>(defaultFormat, "NavigationFormatString");
			}


			set { Set(value, "NavigationFormatString"); }
		}

		/// <summary>
		/// This provides access to basic navigation functions. Platform specific navigation functions are not exposed by this interface, but they may be exposed by the concrete type.
		/// </summary>
		public INavigator Navigator
		{
			get
			{
				return m_Navigator;
			}
			private set
			{
				if (m_Navigator == value)
					return;
				m_Navigator = value;
				OnPropertyChanged("Navigator");
			}
		}

		/// <summary>
		/// The commandParameter associated with this command is used as the target view name
		/// </summary>
		public ICommand NavigateCommand
		{
			get { return GetCommand<string>(viewName => Navigate(viewName), "NavigateCommand"); }
		}

		//[SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic"), SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
		//[ContractInvariantMethod]
		//void ContractInvariant()
		//{
		//	Contract.Invariant(m_Navigator != null);
		//}


		/// <summary>
		/// Opens the indicated website in an external window.
		/// </summary>
		/// <param name="target"></param>
		public void OpenWebsite(string target)
		{
			if (target == null)
				throw new ArgumentNullException("target", "target is null.");
			if (String.IsNullOrEmpty(target))
				throw new ArgumentException("target is empty.", "target");
			if (Navigator == null)
				throw new InvalidOperationException(NavigatorMissing);
			Contract.EndContractBlock();

			var uri = new Uri(target);
			Contract.Assume(!string.IsNullOrEmpty(uri.ToString()));
			OpenWebsite(uri);
		}

		/// <summary>
		/// Opens the indicated website in an external window.
		/// </summary>
		/// <param name="target"></param>
		public void OpenWebsite(Uri target)
		{
			if (target == null)
				throw new ArgumentNullException("target", "target is null.");
			if (Navigator == null)
				throw new InvalidOperationException(NavigatorMissing);
			Contract.EndContractBlock();

			Contract.Assume(!string.IsNullOrEmpty(target.ToString()));
			m_Navigator.OpenWebsite(target);
		}
	}

#if WPF
	partial class NavigationViewModel
	{
		/// <summary>
		/// Initializes a new instance of the NavigationViewModel class.
		/// </summary>
		public NavigationViewModel(NavigationService navigationService)
		{
			if (navigationService == null)
				throw new ArgumentNullException("navigationService", "navigationService is null.");
			Contract.EndContractBlock();

			m_Navigator = new NavigationServiceNavigator(navigationService);
		}

		/// <summary>
		/// Initializes a new instance of the NavigationViewModel class.
		/// </summary>
		public NavigationViewModel(NavigationWindow navigationWindow)
		{
			if (navigationWindow == null)
				throw new ArgumentNullException("navigationWindow", "navigationWindow is null.");
			if (navigationWindow.NavigationService == null)
				throw new ArgumentNullException("navigationWindow", "navigationWindow.NavigationService is null.");
			Contract.EndContractBlock();

			m_Navigator = new NavigationServiceNavigator(navigationWindow.NavigationService);
		}

		/// <summary>
		/// Initializes a new instance of the NavigationViewModel class.
		/// </summary>
		public NavigationViewModel(Frame frame)
		{
			if (frame == null)
				throw new ArgumentNullException("frame", "frame is null.");
			if (frame.NavigationService == null)
				throw new ArgumentNullException("frame", "frame.NavigationService is null.");
			Contract.EndContractBlock();

			m_Navigator = new NavigationServiceNavigator(frame.NavigationService);
		}


		/// <summary>
		/// Changes the navigator associated with this view-model
		/// </summary>
		public void SetNavigator(NavigationService navigationService)
		{
			if (navigationService == null)
				throw new ArgumentNullException("navigationService", "navigationService is null.");
			Contract.EndContractBlock();

			Navigator = new NavigationServiceNavigator(navigationService);
		}

		/// <summary>
		/// Changes the navigator associated with this view-model
		/// </summary>
		public void SetNavigator(NavigationWindow navigationWindow)
		{
			if (navigationWindow == null)
				throw new ArgumentNullException("navigationWindow", "navigationWindow is null.");
			if (navigationWindow.NavigationService == null)
				throw new ArgumentNullException("navigationWindow", "navigationWindow.NavigationService is null.");
			Contract.EndContractBlock();

			Navigator = new NavigationServiceNavigator(navigationWindow.NavigationService);
		}

		/// <summary>
		/// Changes the navigator associated with this view-model
		/// </summary>
		public void SetNavigator(Frame frame)
		{
			if (frame == null)
				throw new ArgumentNullException("frame", "frame is null.");
			if (frame.NavigationService == null)
				throw new ArgumentNullException("frame", "frame.NavigationService is null.");
			Contract.EndContractBlock();

			Navigator = new NavigationServiceNavigator(frame.NavigationService);
		}

	}
#endif

#if SILVERLIGHT && !WINDOWS_PHONE
	public partial class NavigationViewModel : ViewModelBase
	{

		/// <summary>
		/// Initializes a new instance of the NavigationViewModel class.
		/// </summary>
		public NavigationViewModel(Frame frame)
		{
			if (frame == null)
				throw new ArgumentNullException("frame", "frame is null.");
			Contract.EndContractBlock();

			m_Navigator = new FrameNavigator(frame);
		}

		/// <summary>
		/// Changes the navigator associated with this view-model
		/// </summary>
		public void SetNavigator(Frame frame)
		{
			if (frame == null)
				throw new ArgumentNullException("frame", "frame is null.");
			Contract.EndContractBlock();

			Navigator = new FrameNavigator(frame);
		}
	}
#endif

#if WINDOWS_PHONE

	public partial class NavigationViewModel : ViewModelBase
	{
		/// <summary>
		/// Initializes a new instance of the NavigationViewModel class.
		/// </summary>
		[CLSCompliant(false)]
		public NavigationViewModel(PhoneApplicationFrame frame)
		{
			if (frame == null)
				throw new ArgumentNullException("frame", "frame is null.");
			Contract.EndContractBlock();

			m_Navigator = new PhoneApplicationFrameNavigator(frame);
		}

		/// <summary>
		/// Changes the navigator associated with this view-model
		/// </summary>
		[CLSCompliant(false)]
		public void SetNavigator(PhoneApplicationFrame frame)
		{
			if (frame == null)
				throw new ArgumentNullException("frame", "frame is null.");
			Contract.EndContractBlock();

			Navigator = new PhoneApplicationFrameNavigator(frame);
		}	
	}
#endif




}
